#include "odbc/statement.hpp"
#include "odbc/data_reader.hpp"
#include "mysql_test_fixture.hpp"
#include "mysql_record_sample.hpp"
#include "../odbc_test_defs.hpp"
#include <boost/test/auto_unit_test.hpp>

namespace
{
using namespace odbc;
using namespace odbc_tests;

// TODO: Include bit field in tests

BOOST_AUTO_TEST_CASE( testInsertStatement)
{
	// Insert one record
	//		Add parameters
	//		Execute insert
	// Query for the inserted record
	//		Bind parameters
	//		Execute query
	//		Check resulted record
	BOOST_CHECKPOINT( "Testing Insert statement");
	odbc_tests::mysql_test_fixture f;

	using namespace boost::posix_time;

	record_sample r( 20, false, 30, 40, 50, 60, 70.5, 80.35, "Some varchar stuff", 
		boost::posix_time::ptime( boost::gregorian::date( 2008, 05, 22), hours(10)+minutes(20)+seconds(30)));

	BOOST_CHECKPOINT( "Insert one record");
	const std::string insertStatementText( 
		"INSERT INTO dbtype_test_data( "
			"TINYINT_FIELD, BOOL_FIELD, SMALLINT_FIELD, MEDIUMINT_FIELD, INT_FIELD, BIGINT_FIELD, FLOAT_FIELD, DOUBLE_FIELD, VARCHAR_FIELD, DATETIME_FIELD"
			") VALUES( "
			"?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
	statement stat( f.conn(), insertStatementText);

	BOOST_CHECKPOINT( "Add parameters");
	r.add_param_to_statement( stat);

	BOOST_CHECKPOINT( "Execute insert");
	ODBCTEST_CHECK_NOTHROW( stat.execute());

	BOOST_CHECKPOINT( "Query for inserted record");
	const std::string readStatementText(
		"SELECT TINYINT_FIELD, BOOL_FIELD, SMALLINT_FIELD, MEDIUMINT_FIELD, INT_FIELD, BIGINT_FIELD, FLOAT_FIELD, DOUBLE_FIELD, VARCHAR_FIELD, DATETIME_FIELD "
		"FROM dbtype_test_data "
		"WHERE "
			"TINYINT_FIELD = ? "
			"AND BOOL_FIELD = ? "
			"AND SMALLINT_FIELD = ? "
			"AND MEDIUMINT_FIELD = ? "
			"AND INT_FIELD = ? "
			"AND BIGINT_FIELD = ? "
			"AND FLOAT_FIELD = ? "
			"AND DOUBLE_FIELD = ? "
			"AND VARCHAR_FIELD = ? "
			);
	statement query( f.conn(), readStatementText);

	BOOST_CHECKPOINT( "Bind parameters for query");
	query.add_parameter( r.tinyIntValue_);
	query.add_parameter( r.boolValue_);
	query.add_parameter( r.smallIntValue_);
	query.add_parameter( r.mediumIntValue_);
	query.add_parameter( r.intValue_);
	query.add_parameter( r.bigIntValue_);
	query.add_parameter( r.floatValue_);
	query.add_parameter( r.doubleValue_);
	query.add_parameter( r.varcharValue_);

	BOOST_CHECKPOINT( "Execute query");
	ODBCTEST_CHECK_NOTHROW( query.execute());

	data_reader< queried_record> reader( query);
	BOOST_CHECK_EQUAL( 10, reader.numberOfColumns());
	data_reader< queried_record>::const_iterator iter = reader.begin(), endI = reader.end();
	BOOST_REQUIRE( iter != endI);

	ODBCTEST_CHECK_RECORD( iter, r);

	++iter;
	BOOST_CHECK( iter == endI);
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testInsertingNullValues)
{
	// Insert one record
	//		Add null parameters
	//		Execute insert
	// Query for the inserted record
	//		Bind parameters
	//		Execute query
	//		Check resulted record

	BOOST_CHECKPOINT( "Testing Insert statement");
	odbc_tests::mysql_test_fixture f;

	BOOST_CHECKPOINT( "Insert one record");
	const std::string insertStatementText( 
		"INSERT INTO dbtype_test_data( "
			"TINYINT_FIELD, BOOL_FIELD, SMALLINT_FIELD, MEDIUMINT_FIELD, INT_FIELD, BIGINT_FIELD, FLOAT_FIELD, DOUBLE_FIELD, VARCHAR_FIELD, DATETIME_FIELD "
		") VALUES( "
			"?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
	statement stat( f.conn(), insertStatementText);

	using namespace boost::posix_time;

	BOOST_CHECKPOINT( "Add null parameters");
	record_sample::add_null_param_to_statement( stat);

	BOOST_CHECKPOINT( "Execute insert");
	ODBCTEST_CHECK_NOTHROW( stat.execute());

	BOOST_CHECKPOINT( "Query for the inserted record");
	const std::string readStatementText(
		"SELECT TINYINT_FIELD, BOOL_FIELD, SMALLINT_FIELD, MEDIUMINT_FIELD, INT_FIELD, BIGINT_FIELD, FLOAT_FIELD, DOUBLE_FIELD, VARCHAR_FIELD, DATETIME_FIELD "
			"FROM dbtype_test_data "
		"WHERE "
			"TINYINT_FIELD IS NULL "
			"AND BOOL_FIELD IS NULL "
			"AND SMALLINT_FIELD IS NULL "
			"AND MEDIUMINT_FIELD IS NULL "
			"AND INT_FIELD IS NULL "
			"AND BIGINT_FIELD IS NULL "
			"AND FLOAT_FIELD IS NULL "
			"AND DOUBLE_FIELD IS NULL "
			"AND VARCHAR_FIELD IS NULL "
		);
	statement query( f.conn(), readStatementText);

	BOOST_CHECKPOINT( "Execute query");
	ODBCTEST_CHECK_NOTHROW( query.execute());
	
	BOOST_CHECKPOINT( "Check resulted record");
	data_reader< queried_record> reader( query);
	data_reader< queried_record>::const_iterator iter = reader.begin(), endI = reader.end();
	BOOST_REQUIRE( iter != endI);
	ODBCTEST_CHECK_NULL_RECORD( iter);

	++iter;
	BOOST_CHECK( iter == endI);
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testSettingParameterValues)
{
	// Create an insert statement. 
	//		Add the parameters.
	//		Execute the statement
	// On the same insert statement, set the new parameter values.
	//		Execute the statement
	// Query the results
	//		Expect two records to be produced.

	using namespace boost::posix_time;

	odbc_tests::mysql_test_fixture f;

	BOOST_CHECKPOINT( "Create an insert statement");
	record_sample r( 20, false, 30, 40, 50, 60, 70.5, 80.35, "Some varchar stuff", 
		boost::posix_time::ptime( boost::gregorian::date( 2008, 05, 22), hours(10)+minutes(20)+seconds(30)));
	record_sample r1( 21, true, 31, 41, 51, 61, 71.5, 81.35, "Some varchar stuff reloaded", 
		boost::posix_time::ptime( boost::gregorian::date( 2008, 12, 31), hours(10)+minutes(20)+seconds(30)));

	const std::string insertStatementText( 
		"INSERT INTO dbtype_test_data( "
		"TINYINT_FIELD, BOOL_FIELD, SMALLINT_FIELD, MEDIUMINT_FIELD, INT_FIELD, BIGINT_FIELD, FLOAT_FIELD, DOUBLE_FIELD, VARCHAR_FIELD, DATETIME_FIELD"
		") VALUES( "
		"?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
	statement stat( f.conn(), insertStatementText);

	BOOST_CHECKPOINT( "Add the parameters");
	r.add_param_to_statement( stat);

	BOOST_CHECKPOINT( "Execute the statement");
	ODBCTEST_CHECK_NOTHROW( stat.execute());

	BOOST_CHECKPOINT( "Set new parameters");
	r1.set_param_to_statement( stat);

	BOOST_CHECKPOINT( "Execute the statement with the new parameters");
	ODBCTEST_CHECK_NOTHROW( stat.execute());

	BOOST_CHECKPOINT( "Read the results");
	const std::string readStatementText(
		"SELECT TINYINT_FIELD, BOOL_FIELD, SMALLINT_FIELD, MEDIUMINT_FIELD, INT_FIELD, BIGINT_FIELD, FLOAT_FIELD, DOUBLE_FIELD, VARCHAR_FIELD, DATETIME_FIELD "
		"FROM dbtype_test_data ORDER BY 1");

	statement query( f.conn(), readStatementText);

	query.execute();

	data_reader< queried_record> reader( query);
	data_reader< queried_record>::const_iterator iter = reader.begin(), endI = reader.end();
	BOOST_REQUIRE( iter != endI);

	BOOST_CHECKPOINT( "Check the results");
	ODBCTEST_CHECK_RECORD( iter, r);
	++iter;
	BOOST_REQUIRE( iter != endI);
	ODBCTEST_CHECK_RECORD( iter, r1);
	++iter;
	BOOST_CHECK( iter == endI);
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testSettingNullParameterValues)
{
	// Create an insert statement. 
	//		Add the parameters.
	//		Execute the statement
	// On the same insert statement, set the new parameter values as nulls.
	//		Execute the statement
	// Query the results
	//		Expect two records to be produced.

	using namespace boost::posix_time;

	odbc_tests::mysql_test_fixture f;

	BOOST_CHECKPOINT( "Create an insert statement");
	record_sample r( 20, false, 30, 40, 50, 60, 70.5, 80.35, "Some varchar stuff", 
		boost::posix_time::ptime( boost::gregorian::date( 2008, 05, 22), hours(10)+minutes(20)+seconds(30)));

	const std::string insertStatementText( 
		"INSERT INTO dbtype_test_data( "
		"TINYINT_FIELD, BOOL_FIELD, SMALLINT_FIELD, MEDIUMINT_FIELD, INT_FIELD, BIGINT_FIELD, FLOAT_FIELD, DOUBLE_FIELD, VARCHAR_FIELD, DATETIME_FIELD"
		") VALUES( "
		"?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
	statement stat( f.conn(), insertStatementText);

	BOOST_CHECKPOINT( "Add the parameters");
	r.add_param_to_statement( stat);
	BOOST_CHECKPOINT( "Execute the statement");
	ODBCTEST_CHECK_NOTHROW( stat.execute());

	BOOST_CHECKPOINT( "Set new parameters with null values");
	record_sample::set_null_param_to_statement( stat);

	BOOST_CHECKPOINT( "Execute the statement with the new parameters");
	ODBCTEST_CHECK_NOTHROW( stat.execute());

	BOOST_CHECKPOINT( "Read the results");
	const std::string readStatementText(
		"SELECT TINYINT_FIELD, BOOL_FIELD, SMALLINT_FIELD, MEDIUMINT_FIELD, INT_FIELD, BIGINT_FIELD, FLOAT_FIELD, DOUBLE_FIELD, VARCHAR_FIELD, DATETIME_FIELD "
		"FROM dbtype_test_data ORDER BY 1");
	statement query( f.conn(), readStatementText);
	query.execute();

	data_reader< queried_record> reader( query);
	data_reader< queried_record>::const_iterator iter = reader.begin(), endI = reader.end();
	BOOST_REQUIRE( iter != endI);

	BOOST_CHECKPOINT( "Check the results");
	ODBCTEST_CHECK_NULL_RECORD( iter);
	++iter;
	BOOST_REQUIRE( iter != endI);
	ODBCTEST_CHECK_RECORD( iter, r);
	++iter;
	BOOST_CHECK( iter == endI);
}

} // namespace
